home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Nebula 2
/
Nebula Two.iso
/
SourceCode
/
Database
/
SimpleTableView-1
/
DataTable.m
< prev
next >
Wrap
Text File
|
1995-06-12
|
18KB
|
711 lines
// -------------------------------------------------------------------------------------
// DataTable
// This software is without warranty of any kind. Use at your own risk.
// -------------------------------------------------------------------------------------
#import <objc/objc.h>
#import <appkit/appkit.h>
#import <mach/mach.h>
#import <dbkit/dbkit.h>
#import <libc.h>
#import <stdio.h>
#import <string.h>
#import <ctype.h>
#import "DataTable.h"
// -------------------------------------------------------------------------------------
// misc
#define KEY_COLUMN 0
// -------------------------------------------------------------------------------------
// string macros
#define freeString(S) { if (S) { free(S); S = (char*)nil; } }
// -------------------------------------------------------------------------------------
// table macros
#define dataROWS tableHandle->dataId
#define columnINFO tableHandle->columnId
#define orderedINFO tableHandle->reorderId
// -------------------------------------------------------------------------------------
@interface DataTable(private)
- _resetDisplayedColumnOrdering;
@end
// -------------------------------------------------------------------------------------
@implementation DataTable
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
// initialize connection with data table
/* open table */
- initFromFile:(const char*)fileName
{
char *p = fileName? rindex((char*)fileName, '/') : (char*)nil;
/* init super */
[self init];
nullIndex = (id)-1;
delegate = (id)nil;
isModified = NO;
/* initialize table handle */
tableHandle = (dataTable_t*)malloc(sizeof(dataTable_t));
memset(tableHandle, 0, sizeof(dataTable_t));
tableHandle->name = NXCopyStringBuffer(p? (p+1) : "Unknown");
tableHandle->access = fileName? NXCopyStringBuffer(fileName) : (char*)nil;
tableHandle->viewSize.width = tableHandle->viewSize.height = 0.0;
tableHandle->columnId = [[[List alloc] initCount:1] empty];
tableHandle->reorderId = (id)nil;
tableHandle->dataId = [[List allocFromZone:[self zone]] init];
tableHandle->date = [self timestamp];
/* read column-info and data */
if (![self readTableColumns] || ![self readTableData])
NXLogError("Unable to load column/data for file %s", (fileName?fileName:"?"));
/* return */
return self;
}
// -------------------------------------------------------------------------------------
// set attributes
/* set delegate */
- setDelegate:aDelegate
{
delegate = aDelegate;
return self;
}
/* return current delegate */
- delegate
{
return delegate;
}
/* null index */
- setNullIndex:index
{
nullIndex = index;
return self;
}
/* set number of rows */
- setRows:(u_int)rowCount
{
return [self notImplemented:_cmd];
}
/* set number of columns */
- setColumns:(u_int)colCount
{
return [self notImplemented:_cmd];
}
/* set display order for column */
- setDisplayOrder:(int)order andWidth:(float)width forColumnIndex:(int)column
{
BOOL didEdit = NO;
dataColumn_t *ci = [self columnInfoAt:column];
if (!ci) return (id)nil;
if (ci->displayOrder != order) {
ci->displayOrder = order;
ci->isHidden = (order >= 0)? NO : YES;
didEdit = YES;
if (tableHandle->reorderId) {
[tableHandle->reorderId free];
tableHandle->reorderId = (id)nil;
}
}
if ((width >= 0.0) && (ci->size != width)) {
ci->size = width;
didEdit = YES;
}
if (didEdit) [self setDocEdited:YES];
return self;
}
// -------------------------------------------------------------------------------------
// freeing resources
/* free columns */
- _freeColumnInfo
{
int c;
for (c = 0; c < [columnINFO count]; c++) {
dataColumn_t *ci = [self columnInfoAt:c];
freeString(ci->keyTag);
freeString(ci->title);
freeString(ci->nilValue);
free(ci);
}
[columnINFO free];
columnINFO = (id)nil;
if (orderedINFO) {
[orderedINFO free];
orderedINFO = (id)nil;
}
return self;
}
/* free a single row */
- _freeRow:rowId
{
int c;
for (c = 0; c < [rowId count]; c++) {
dataEntry_t *de = entryPTR(rowId, c);
freeString(de->value);
free(de);
}
return self;
}
/* free data */
- _freeData
{
int r;
for (r = 0; r < [dataROWS count]; r++) [self _freeRow:[dataROWS objectAt:r]];
[dataROWS freeObjects];
[dataROWS free];
dataROWS = (id)nil;
return self;
}
/* free */
- free
{
/* free table */
if (tableHandle) {
freeString(tableHandle->name);
freeString(tableHandle->access);
[self _freeData];
[self _freeColumnInfo];
free(tableHandle);
}
/* free object */
return [super free];
}
// -------------------------------------------------------------------------------------
// View size (handle by superclass, delegate, or other object)
/* set size */
- setViewSize:(NXSize*)size
{
tableHandle->viewSize = *size;
return self;
}
/* get size */
- (const NXSize*)viewSize
{
if ((tableHandle->viewSize.width > 0.0) && (tableHandle->viewSize.height > 0.0))
return &(tableHandle->viewSize);
return (NXSize*)nil;
}
// -------------------------------------------------------------------------------------
// saving the table
/* check last modified date */
- (BOOL)tableHasChanged
{
struct stat st;
/* stat file */
if (!tableHandle->access) return NO;
if (stat((char*)tableHandle->access, &st) < 0) {
NXLogError("Unable to stat file %s", tableHandle->access);
return YES;
}
/* return file changed status */
return tableHandle->date == st.st_mtime? NO : YES;
}
/* indicate table has been edited */
- setDocEdited:(BOOL)flag
{
isModified = flag;
if (delegate && (delegate != self) && [delegate respondsTo:@selector(setDocEdited:)])
[delegate setDocEdited:flag];
return self;
}
/* commit/save table */
- commitTable
{
/* make sure we have a file name */
if (!tableHandle->access) {
// load table name */
}
/* write entire table */
if ([self writeTable]) [self setDocEdited:NO];
return self;
}
// -------------------------------------------------------------------------------------
// column info
/* return column info pointer */
+ (dataColumn_t*)_column:infoId infoAt:(int)n
{
if (!infoId || (n < 0) || (n >= [infoId count])) return (dataColumn_t*)nil;
return (dataColumn_t*)[infoId objectAt:n];
}
/* get column info by index */
- (dataColumn_t*)columnInfoAt:(u_int)col
{
return [[self class] _column:columnINFO infoAt:col];
}
/* return column number for matching keyTag */
- (int)indexForColumnName:(const char*)name
{
int c;
if (!name || !columnINFO) return -1;
for (c = 0; c < [columnINFO count]; c++) {
dataColumn_t *ci = [self columnInfoAt:c];
if (!strcmp(name, ci->keyTag)) return c;
}
return -1;
}
/* reset displayed column ordering */
- _resetDisplayedColumnOrdering
{
int c, cnt;
/* empty reorder table */
if (!orderedINFO) orderedINFO = [[List alloc] initCount:1];
[orderedINFO empty];
/* add visible column headers to new list */
for (c = 0, cnt = [columnINFO count]; c < cnt; c++) {
dataColumn_t *ci = [self columnInfoAt:c];
if (!ci || (ci->displayOrder < 0)) continue;
[orderedINFO addObject:(id)ci];
}
/* sort reorder table by display order (since table is small, use simple-sort) */
for (c = 0, cnt = [orderedINFO count]; c < cnt; c++) {
int n;
dataColumn_t *ci = (dataColumn_t*)[orderedINFO objectAt:c];
for (n = c + 1; n < cnt; n++) {
dataColumn_t *ni = (dataColumn_t*)[orderedINFO objectAt:n];
if (ci->displayOrder > ni->displayOrder) {
[orderedINFO replaceObjectAt:c with:(id)ni];
[orderedINFO replaceObjectAt:n with:(id)ci];
ci = ni;
}
}
}
return self;
}
/* get column info by order */
- (dataColumn_t*)orderedColumnInfoAt:(u_int)ord
{
if (!orderedINFO) [self _resetDisplayedColumnOrdering];
return [[self class] _column:orderedINFO infoAt:ord];
}
/* add column info */
- addColumnInfo:(dataColumn_t*)dc
{
if (dc->type == CDT_UNKNOWN) dc->type = CDT_STRING;
if (!dc->title) dc->title = NXCopyStringBuffer(dc->keyTag);
if (dc->minSize <= 0.0) dc->minSize = dc->size;
[columnINFO addObject:(id)dc];
return self;
}
// -------------------------------------------------------------------------------------
// load text file table data
/* duplicate row */
- newRowName:(const char*)rowN copyFromRow:(int)rowX
{
int c, rcnt = [dataROWS count], ccnt = [columnINFO count];
id rowId, newRow;
if ((rowX < 0) || (rowX > rcnt)) rowX = rcnt;
rowId = rowX < rcnt? [dataROWS objectAt:rowX] : (id)nil;
newRow = [[[List alloc] initCount:0] empty];
if (![dataROWS insertObject:newRow at:MIN(rowX + 1, rcnt)]) {
NXLogError("Unable to add new row %s into table", rowN);
[newRow free];
return (id)nil;
}
for (c = 0; c < ccnt; c++) {
dataEntry_t *ne = entryNEW, *oe = rowId? entryPTR(rowId, c) : (dataEntry_t*)nil;
ne->value = (char*)[self copyStringValue:(c?(oe?oe->value:""):rowN) forColumn:c];
ne->isValid = -1;
[newRow insertObject:(id)ne at:c];
}
[self setDocEdited:YES];
return newRow;
}
/* delete row */
- deleteRowAt:(int)rowX
{
id rowId;
if ((rowX < 0) || (rowX >= [dataROWS count])) return (id)nil;
if (!(rowId = [dataROWS removeObjectAt:rowX])) return (id)nil;
[self _freeRow:rowId];
[self setDocEdited:YES];
return self;
}
/* remove all table entries */
- empty
{
return self;
}
/* fill/refill table */
- reset
{
return self;
}
// -------------------------------------------------------------------------------------
// sorting
/* sort comparison */
- (int)sortCompare:(dataColumn_t*)dc values:(const char*)val1:(const char*)val2
{
if (dc->type == CDT_INTEGER) return atoi(val1) - atoi(val2);
return strcasecmp(val1, val2);
}
/* sort data table by column */
#define STRCOMP(C,T,R1,R2) [self sortCompare:T values:entryVALUE(R1,C):entryVALUE(R2,C)]
//#define STRCOMP(C,T,R1,R2) strcasecmp(entryVALUE(R1,C),entryVALUE(R2,C))
- sortTableByColumn:(int)pri :(int)sec
{
int r, cnt;
dataColumn_t *dcp, *dcs;
/* check sort keys */
if (pri < 0) {
NXLogError("Invalid primary sort column %d", pri);
return (id)nil;
}
if (pri == KEY_COLUMN) sec = -1;
/* get column type */
dcp = [self columnInfoAt:pri];
dcs = (sec >= 0)? [self columnInfoAt:sec] : (dataColumn_t*)nil;
/* sort */
for (r = 0, cnt = [dataROWS count]; r < cnt; r++) {
int n;
id rowR = [dataROWS objectAt:r];
for (n = r + 1; n < cnt; n++) {
id rowN = [dataROWS objectAt:n];
int cmp = STRCOMP(pri,dcp,rowR,rowN);
if ((cmp > 0) || ((cmp == 0) && dcs && (STRCOMP(sec,dcs,rowR,rowN) > 0))) {
[dataROWS replaceObjectAt:r with:rowN];
[dataROWS replaceObjectAt:n with:rowR];
rowR = rowN;
}
}
}
return self;
}
#undef STRCOMP
/* sort data table by column */
- sortTableByColumnName:(const char*)priName :(const char*)secName
{
int pri = [self indexForColumnName:priName];
int sec = secName? [self indexForColumnName:secName] : -1;
/* check sort keys */
if (pri < 0) {
NXLogError("Invalid primary sort key %s", (priName?priName:"?"));
return (id)nil;
}
if (secName && (sec < 0)) {
NXLogError("Invalid secondary sort key %s", secName);
return (id)nil;
}
/* sort */
return [self sortTableByColumn:pri:sec];
}
// -------------------------------------------------------------------------------------
// validate entries
/* validate specific entry */
- (BOOL)_validateEntry:(dataEntry_t*)de forColumn:(u_int)column
{
if (!de) return NO;
if (de->isValid < 0) {
dataColumn_t *dc = [self columnInfoAt:column];
if (dc->nilValue&&(!*de->value||!strcmp(de->value,dc->nilValue))) de->isValid = YES;
else de->isValid = [self verifyValue:de->value dataType:dc->type]? 1 : 0;
}
return de->isValid;
}
/* validate entries until an error is found */
- (BOOL)hasVerificationErrors
{
int r, rcnt = [dataROWS count];
for (r = 0; r < rcnt; r++) {
id rowId = [dataROWS objectAt:r];
int c, ccnt = [rowId count];
for (c = 0; c < ccnt; c ++) {
dataEntry_t *de = entryPTR(rowId,c);
if (![self _validateEntry:de forColumn:c]) return YES;
}
}
return NO;
}
/* validate entry at specific location */
- (BOOL)verifyValueAt:(u_int)row :(u_int)column
{
return [self _validateEntry:[self entryAtIndex:row:column] forColumn:column];
}
// -------------------------------------------------------------------------------------
// table access
/* find row for specified name */
- (int)indexForRowName:(const char*)rowN exactMatch:(BOOL)exact
{
int r;
if (!rowN) return -1; // not found
for (r = 0; r < [dataROWS count]; r++) {
id rowId = [dataROWS objectAt:r];
char *rn = (char*)entryVALUE(rowId,0);
if (exact) { if (!strcmp(rn, rowN)) return r; }
else { if (!strstr(rn, rowN)) return r; }
}
return -1;
}
/* return specified table entry (by index) */
- (dataEntry_t*)entryAtIndex:(u_int)rowX :(u_int)colX
{
id rowId = [dataROWS objectAt:rowX];
if (!rowId || (colX > [rowId count])) return (dataEntry_t*)nil;
return entryPTR(rowId,colX);
}
/* return specified table entry (by name) */
- (const char*)valueForRowName:(const char*)rowN columnName:(const char*)colN
{
int wc = [self indexForColumnName:colN];
if (wc >= 0) {
int r = [self indexForRowName:rowN exactMatch:YES];
if (r >= 0) return entryVALUE([dataROWS objectAt:r], wc);
}
return (char*)nil;
}
/* return specified table entry (by index) */
- (const char*)valueAtIndex:(u_int)rowX :(u_int)colX
{
dataEntry_t *de = [self entryAtIndex:rowX:colX];
return de? de->value : (char*)nil;
}
/* return string constant at location */
- (const char*)valueFor:(u_int)rowIndex :(u_int)columnIndex
{
return [self valueAtIndex:rowIndex:columnIndex];
}
/* set indexed table entry */
- setValue:(const char*)value atIndex:(u_int)rowX :(u_int)colX
{
dataEntry_t *de = [self entryAtIndex:rowX:colX];
if (!de) return (id)nil;
freeString(de->value);
de->value = (char*)[self copyStringValue:value forColumn:colX];
de->isValid = -1;
[self setDocEdited:YES];
return self;
}
/* set value for specified row/column name */
- setValue:(const char*)value forRowName:(const char*)rowN columnName:(const char*)colN
{
int wc = [self indexForColumnName:colN];
if (wc >= 0) {
int wr = [self indexForRowName:rowN exactMatch:YES];
if (wr >= 0) return [self setValue:value atIndex:wr:wc];
}
return (id)nil;
}
/* change table value (note: index is independent of actual column position) */
- setValueFor:rowIndex :colIndex from:aValue
{
return [self setValue:[aValue stringValue] atIndex:(u_int)rowIndex:(u_int)colIndex];
}
/* change table value (note: index is independent of actual column position) */
- setValueFor:colIndex at:(u_int)rowPosition from:aValue
{
return [self setValue:[aValue stringValue] atIndex:rowPosition:(u_int)colIndex];
}
/* get table value */
- getValueFor:rowIndex :columnIndex into:aValue
{
return [self getValueFor:columnIndex at:(u_int)rowIndex into:aValue];
}
/* get table value */
- getValueFor:index at:(u_int)aPosition into:aValue
{
if (index == nullIndex) { [aValue setNull]; return self; }
[aValue setStringValue:(char*)[self valueFor:aPosition :(u_int)index]];
return self;
}
/* return specified table entry (by name) */
- (int)scanForValue:(const char*)value inColumnName:(const char*)colN
startingAtRow:(int)rowX backwards:(BOOL)back
{
if (value && colN) {
int wc = [self indexForColumnName:colN];
if (wc >= 0) {
int r = rowX < 0? [dataROWS count] : rowX % [dataROWS count];
if (rowX < 0) r = [dataROWS count];
else r = rowX % [dataROWS count];
for (;;) {
id rowId = [dataROWS objectAt:r];
if (strstr(entryVALUE(rowId, wc), value)) return r;
r = (r + 1) % [dataROWS count];
if (r == rowX) break;
}
}
}
return -1;
}
// -------------------------------------------------------------------------------------
// return table attributes
/* return name of table */
- (const char*)tableName
{
return tableHandle->name;
}
/* return title of table */
- (const char*)tableTitle
{
return tableHandle->name;
}
/* return access(path) of table */
- (const char*)tableAccess
{
return tableHandle->access;
}
/* return number of visible columns in table */
- (u_int)visibleColumnCount
{
if (!orderedINFO) [self _resetDisplayedColumnOrdering];
return [orderedINFO count];
}
/* return number of visible columns */
- (u_int)columnCount
{
return [self visibleColumnCount];
}
/* return number of visible columns in table */
- (u_int)actualColumnCount
{
return [columnINFO count];
}
/* return number of rows in table */
- (u_int)rowCount
{
return [dataROWS count];
}
// -------------------------------------------------------------------------------------
// DBTableView notification methods
/* user selection changed */
- tableViewDidChangeSelection:aTableView
{
return self;
}
/* user move column */
- tableView:aTableView movedColumnFrom:(u_int)oldpos to:(u_int)newpos
{
[self setDocEdited:YES];
return self;
}
/* selection will change */
- (BOOL)tableViewWillChangeSelection:aTableView
{
return YES; // allow selection change
}
// -------------------------------------------------------------------------------------
// subclass methods
- (time_t)timestamp
{
return (time_t)0;
}
- readTableColumns
{
return [self subclassResponsibility:_cmd];
}
- readTableData
{
return [self subclassResponsibility:_cmd];
}
- writeTable
{
return [self subclassResponsibility:_cmd];
}
- (const char*)copyStringValue:(const char*)value forColumn:(int)index
{
return NXCopyStringBuffer(value? value : "");
}
- (BOOL)verifyValue:(const char*)value dataType:(int)dataType
{
return value? YES : NO;
}
// -------------------------------------------------------------------------------------
@end